import re
import chardet
from typing import Tuple, List, Dict
from Tools.filter_def_names import func_names, filter_func_names

def detect_encoding(file_path):
    with open(file_path, 'rb') as f:
        result = chardet.detect(f.read())
    return result['encoding']
    

def process_file(filename: str) -> Tuple[List[Dict], Dict[str, str]]:
    """解析Fortran文件，提取程序结构及调用参数，并收集别名关系"""
    chunks = []
    current_chunk = None
    # Modified call pattern to better match calls with or without parentheses
    call_pattern = re.compile(r'\bcall\s+([a-zA-Z_]\w*)\s*(?:\((.*?)(?:\)|\Z)|$)', re.IGNORECASE|re.DOTALL)
    comment_pattern = re.compile(r'!.*$')
    string_pattern = re.compile(r'["\'].*?["\']')
    
    # Debug flag
    debug = False
    
    # 别名关系的正则表达式
    use_alias_pattern = re.compile(r'\buse\s+\w+\s*,\s*(\w+)\s*=>\s*(\w+)', re.IGNORECASE)
    procedure_alias_pattern = re.compile(r'\bprocedure\s*::\s*(\w+)\s*=>\s*(\w+)', re.IGNORECASE)
    generic_alias_pattern = re.compile(r'\bgeneric\s*::\s*(\w+)\s*=>\s*([\w\s,]+)', re.IGNORECASE)

    # 别名映射字典
    aliases = {}

    try:
        enc = detect_encoding(filename)
        with open(filename, 'r', encoding=enc, errors='ignore') as f:
            lines = f.readlines()
    except Exception as e:
        if debug:
            print(f"Error reading file {filename}: {e}")
        return [], {}

    stack = []
    
    if debug and filename.endswith(('.F', '.F90')):
        print(f"Debug: Processing file {filename}, found {len(lines)} lines")

    for line_num, line in enumerate(lines, 1):
        original_line = line
        line_without_comments = comment_pattern.sub('', line)
        line = string_pattern.sub('', line_without_comments)
        clean_line = line.strip().lower()

        # 收集别名关系
        use_matches = use_alias_pattern.findall(line)
        for alias, original in use_matches:
            aliases[alias.lower()] = original.lower()
            
        procedure_matches = procedure_alias_pattern.findall(line)
        for alias, original in procedure_matches:
            aliases[alias.lower()] = original.lower()
            
        generic_matches = generic_alias_pattern.findall(line)
        for generic_name, methods in generic_matches:
            for method in re.split(r'\s*,\s*', methods.strip()):
                if method.strip():  # 确保不为空
                    aliases[generic_name.lower()] = method.lower()  # 简化处理，对于generic只取第一个方法

        # Check for program or subroutine declaration
        if re.match(r'^\s*(subroutine|program)\s+[a-zA-Z_]', line, re.IGNORECASE):
            if not current_chunk:
                name_match = re.search(r'(subroutine|program)\s+([a-zA-Z_]\w*)', line, re.IGNORECASE)
                if name_match:
                    chunk_type = name_match.group(1).lower()
                    chunk_name = name_match.group(2).lower()
                    if debug and chunk_type == 'program':
                        print(f"Debug: Found program {chunk_name} at line {line_num}")
                    current_chunk = {
                        'type': chunk_type,
                        'name': chunk_name,
                        'start_line': line_num,
                        'calls': []
                    }
                    stack.append(current_chunk)

        # Check for end of program or subroutine
        elif current_chunk and re.match(rf'^\s*end\s+{current_chunk["type"]}\b', line, re.IGNORECASE):
            current_chunk['end_line'] = line_num
            chunks.append(current_chunk)
            if debug and current_chunk['type'] == 'program':
                print(f"Debug: Found end of program {current_chunk['name']} at line {line_num}")
                print(f"Debug: Program {current_chunk['name']} has {len(current_chunk['calls'])} calls")
            stack.pop()
            current_chunk = stack[-1] if stack else None

        # Check for function calls
        if current_chunk:
            # Clean up line and prepare for call detection
            cleaned_line = re.sub(r'[!;].*', '', original_line).strip()
            
            # Ensure there's a space after 'call' and before any parentheses
            # This helps with parsing irregularly formatted calls
            if re.search(r'\bcall\b', cleaned_line, re.IGNORECASE):
                # Modified to better handle cases where call appears at start of line with indentation
                cleaned_line = re.sub(
                    r'(^\s*)call(\s*)([a-zA-Z_]\w*)\s*$',
                    r'\1call \3()',
                    cleaned_line,
                    flags=re.IGNORECASE
                )
                
                # Add space between function name and opening parenthesis if missing
                cleaned_line = re.sub(
                    r'(call\s+[a-zA-Z_]\w*)(\()',
                    r'\1 \2',
                    cleaned_line,
                    flags=re.IGNORECASE
                )
                
                # Debug
                if debug and current_chunk['type'] == 'program':
                    print(f"Debug: Checking line for calls: {cleaned_line}")
                
                # Find calls
                call_match = call_pattern.search(cleaned_line)
                
                if call_match:
                    func_name = call_match.group(1).strip().lower()
                    if debug and current_chunk['type'] == 'program':
                        print(f"Debug: Found call to {func_name}")
                    
                    # Get arguments (if any)
                    args = call_match.group(2).strip() if call_match.group(2) else ""
                    
                    # Handle multi-line arguments
                    if '&' in args:
                        args_lines = [args]
                        line_idx = line_num
                        while line_idx < len(lines) and ('&' in lines[line_idx] or line_idx == line_num):
                            line_idx += 1
                            if line_idx >= len(lines):
                                break
                            next_line_clean = comment_pattern.sub('', lines[line_idx]).strip()
                            if not next_line_clean:
                                continue
                            if next_line_clean.endswith('&'):
                                args_lines.append(next_line_clean[:-1].strip())
                            else:
                                args_lines.append(next_line_clean)
                                break
                        args = ''.join(args_lines).replace('&', '').strip()
                    
                    # Clean up arguments - remove trailing parenthesis
                    args = args[:-1] if args.endswith(')') else args
                    
                    # Filter certain functions
                    if func_name in func_names:
                        continue
                    
                    if func_name.startswith('mpi_') or func_name.startswith('mpl_') or func_name.startswith('timer_') or func_name.startswith('nc_diag'):
                        continue
                    
                    # Special handling for driver_atm_run 
                    if func_name == 'driver_atm_run': 
                        first_arg = args.split(',')[0].strip() if args else ''
                        first_arg = first_arg.strip('\"\'')
                        current_chunk['calls'].append({
                            'func': first_arg.lower() if first_arg else 'unknown',
                            'args': args  # Original arguments
                        })
                    else:
                        current_chunk['calls'].append({
                            'func': func_name,
                            'args': args
                        })
                        if debug and current_chunk['type'] == 'program':
                            print(f"Debug: Added call to {func_name} with args: {args}")

    return chunks, aliases
